package com.listsorter;

import java.lang.reflect.Method;
import java.text.Collator;
import java.util.Comparator;
import java.util.Date;
import java.util.Locale;

/**
 * 
 * @author tamerhur
 * 
 */
public class ListSorter<T> implements Comparator<T>
{

   private String[] customMethodPath;

   private Object[] args = null;

   private T sampleItem;

   SortType sortType;

   OrderType orderType;

   Collator collator;

   public ListSorter(T sampleItem)
   {

      this.sampleItem = sampleItem;
      this.customMethodPath = new String[1];
      this.customMethodPath[0] = "sortOrder";
      this.orderType = OrderType.ASCENDING;
      determineSortType();
   }

   public ListSorter(T sampleItem, Locale locale)
   {

      this.sampleItem = sampleItem;
      this.customMethodPath = new String[1];
      this.customMethodPath[0] = "sortOrder";
      this.orderType = OrderType.ASCENDING;
      this.collator = Collator.getInstance(locale);
      determineSortType();
   }

   public ListSorter(T sampleItem, OrderType orderType)
   {
      this.sampleItem = sampleItem;
      this.customMethodPath = new String[1];
      this.customMethodPath[0] = "sortOrder";
      this.orderType = orderType;
      determineSortType();
   }

   public ListSorter(T sampleItem, OrderType orderType, Locale locale)
   {
      this.sampleItem = sampleItem;
      this.customMethodPath = new String[1];
      this.customMethodPath[0] = "sortOrder";
      this.orderType = orderType;
      this.collator = Collator.getInstance(locale);
      determineSortType();
   }

   public ListSorter(T sampleItem, String[] customMethodPath, OrderType orderType)
   {
      this.sampleItem = sampleItem;
      this.customMethodPath = customMethodPath;
      this.orderType = orderType;
      determineSortType();
   }

   public ListSorter(T sampleItem, String[] customMethodPath, OrderType orderType, Locale locale)
   {
      this.sampleItem = sampleItem;
      this.customMethodPath = customMethodPath;
      this.orderType = orderType;
      this.collator = Collator.getInstance(locale);
      determineSortType();
   }

   public ListSorter(T sampleItem, String[] customMethodPath)
   {
      this.sampleItem = sampleItem;
      this.customMethodPath = customMethodPath;
      this.orderType = OrderType.ASCENDING;
      determineSortType();
   }

   public ListSorter(T sampleItem, String[] customMethodPath, Locale locale)
   {
      this.sampleItem = sampleItem;
      this.customMethodPath = customMethodPath;
      this.orderType = OrderType.ASCENDING;
      this.collator = Collator.getInstance(locale);
      determineSortType();
   }

   private void determineSortType()
   {
      Method method = null;
      try
      {

         method = ReflectionUtil.getMethod(this.customMethodPath, this.sampleItem, args);

      }
      catch (NoSuchMethodException e)
      {
         System.err.println("no method found for sorting please check your sort methods");
      }
      catch (Exception e)
      {
         System.err.println("Exception while getting sort method.");
      }
      String retTypeName = method.getReturnType().getName();

      if (retTypeName.equals("java.lang.String"))
      {
         this.sortType = SortType.STRING;
      }
      else if (retTypeName.equals("long") || retTypeName.equals("java.lang.Long"))
      {
         this.sortType = SortType.LONG;
      }
      else if (retTypeName.equals("int") || retTypeName.equals("java.lang.Integer"))
      {
         this.sortType = SortType.INTEGER;
      }
      else if (retTypeName.equals("double") || retTypeName.equals("java.lang.Double"))
      {
         this.sortType = SortType.DOUBLE;
      }
      else if (retTypeName.equals("float") || retTypeName.equals("java.lang.Float"))
      {
         this.sortType = SortType.FLOAT;
      }
      else if (retTypeName.equals("java.util.Date"))
      {
         this.sortType = SortType.DATE;
      }
   }

   public int compare(T obj1, T obj2)
   {

      if (sortType == SortType.STRING)
      {
         return StringCompare(obj1, obj2, args);
      }
      else if (sortType == SortType.INTEGER)
      {
         return IntegerCompare(obj1, obj2, args);
      }
      else if (sortType == SortType.DATE)
      {
         return DateCompare(obj1, obj2, args);
      }
      else if (sortType == SortType.LONG)
      {
         return LongCompare(obj1, obj2, args);

      }
      else if (sortType == SortType.FLOAT)
      {
         return FloatCompare(obj1, obj2, args);

      }
      else if (sortType == SortType.DOUBLE)
      {
         return DoubleCompare(obj1, obj2, args);
      }
      else
      {

         System.err.println("Sort method return type is not supported");
      }

      return 0;
   }

   public boolean equals()
   {
      return false;
   }

   private Object[] getKeys(T obj1, T obj2, Object[] args)
   {

      Object refKey1 = null;
      Object refKey2 = null;
      try
      {

         refKey1 = ReflectionUtil.invokeObject(this.customMethodPath, obj1, args);
         refKey2 = ReflectionUtil.invokeObject(this.customMethodPath, obj2, args);

      }
      catch (Exception e)
      {
         System.err.println("Error while invoking sort method please check your sort method.");
      }
      Object[] result = new Object[2];
      result[0] = refKey1;
      result[1] = refKey2;
      return result;
   }

   private int DateCompare(T obj1, T obj2, Object[] args)
   {

      Object[] keys = getKeys(obj1, obj2, args);
      long val1 = ((Date) keys[0]).getTime();
      long val2 = ((Date) keys[1]).getTime();
      return (int) (val1 < val2 ? orderType.getValue() : (val1 == val2 ? 0 : orderType.getValue() * -1));

   }

   private int IntegerCompare(T obj1, T obj2, Object[] args)
   {

      Object[] keys = getKeys(obj1, obj2, args);
      int val1 = ((Integer) keys[0]).intValue();
      int val2 = ((Integer) keys[1]).intValue();
      return (int) (val1 < val2 ? orderType.getValue() : (val1 == val2 ? 0 : orderType.getValue() * -1));

   }

   private int LongCompare(T obj1, T obj2, Object[] args)
   {

      Object[] keys = getKeys(obj1, obj2, args);
      long val1 = ((Long) keys[0]).longValue();
      long val2 = ((Long) keys[1]).longValue();
      return (int) (val1 < val2 ? orderType.getValue() : (val1 == val2 ? 0 : orderType.getValue() * -1));

   }

   private int FloatCompare(T obj1, T obj2, Object[] args)
   {

      Object[] keys = getKeys(obj1, obj2, args);
      float val1 = ((Float) keys[0]).floatValue();
      float val2 = ((Float) keys[1]).floatValue();
      return (int) (val1 < val2 ? orderType.getValue() : (val1 == val2 ? 0 : orderType.getValue() * -1));

   }

   private int DoubleCompare(T obj1, T obj2, Object[] args)
   {

      Object[] keys = getKeys(obj1, obj2, args);
      double val1 = ((Double) keys[0]).doubleValue();
      double val2 = ((Double) keys[1]).doubleValue();
      return (int) (val1 < val2 ? orderType.getValue() : (val1 == val2 ? 0 : orderType.getValue() * -1));

   }

   private int StringCompare(T obj1, T obj2, Object[] args)
   {
      Object[] keys = getKeys(obj1, obj2, args);
      String refKey1 = (String) keys[0];
      String refKey2 = (String) keys[1];

      if (this.collator != null)
      {
         int result = collator.compare(refKey1, refKey2);
         return (result < 0 ? orderType.getValue() : (result == 0 ? 0 : orderType.getValue() * -1));
      }
      else
      {

         int len1 = refKey1.length();
         int len2 = refKey2.length();
         int n = Math.min(len1, len2);
         char v1[] = refKey1.toCharArray();
         char v2[] = refKey2.toCharArray();
         int pos = 0;

         while (n-- != 0)
         {
            char c1 = v1[pos];
            char c2 = v2[pos];
            if (c1 != c2)
            {
               return orderType == OrderType.ASCENDING ? c1 - c2 : c2 - c1;
            }
            pos++;
         }
         return orderType == OrderType.ASCENDING ? len1 - len2 : len2 - len1;

      }

   }
}
